//
//  DFUProcess.swift
//  Pods
//
//  Created by tom on 2025/9/29.
//

class DFUProcess {
    let address: String
    let peripheralManager:PeripheralManager
    let filePath: String
    
    init(peripheralManager:PeripheralManager, address:String,filePath: String) {
        self.peripheralManager = peripheralManager
        self.address = address
        self.filePath = filePath
    }
    
    private var continuation:AsyncThrowingStream<DFUStateEvent, Error>.Continuation? = nil
    
    func processing() -> AsyncThrowingStream<DFUStateEvent, Error>{
        return AsyncThrowingStream<DFUStateEvent, Error> {[weak self] continuation in
            self?.continuation = continuation
            let task = Task{
                do{
                    try await withTaskCancellationHandler {
                        try await self?.process()
                    } onCancel: {[weak self] in
                        if let address = self?.address{
                            continuation.yield(DFUStateEvent(
                                address: address,
                                state:.canceled,
                            ))
                        }
                        
                    }

                }catch{
                    continuation.finish(throwing: error)
                    
                }
            }
            continuation.onTermination = {[weak self] _ in
                self?.continuation = nil
                task.cancel()
            }
            
            
        }
    }
    
    private func process()async throws{
        continuation?.yield(DFUStateEvent(address: address, state: DFUState.start))
        
        if !FileManager.default.fileExists(atPath: filePath){
            throw BleError.binFileNotFound(filePath: filePath)
        }
        guard let binFileBytes = FileManager.default.contents(atPath: filePath) else { throw BleError.binFileNotFound(filePath: filePath) }
        let fileSize = binFileBytes.count
        debugPrint("DFUProcess DFU fileSize:\(fileSize)")
        let totalTime:Int = Int(ceil(Double(fileSize) / 1024.0))
        var bytesBuilder = Binary()
        bytesBuilder.writeByte(1)
        bytesBuilder.writeInt(Int32(fileSize), littleEndian: true)
        let checksum:Int = binFileBytes.reduce(0, { last, uint8 in
            last + Int(uint8 & 0xff)
        })
        debugPrint("DFUProcess DFU checksum:\(checksum)")
        bytesBuilder.writeInt(Int32(checksum), littleEndian: true)
        guard let data = try? bytesBuilder.getAllBytes() else {
            throw BleError.dfuDataError(msg: "DFU_INIT 出错")
        }
        var resp = try await peripheralManager.request(address: address, cmd: .dfuInit, data: Data(data))
        if resp.data?.first != ASK_CODE_SUCCESS{
            throw BleError.dfuDataError(msg: "DFU_INIT response error:\(resp.data?.hexEncodedString(separator: "_"))")
        }
        try Task.checkCancellation()
        let binFileChunks = binFileBytes.chunks(ofCount: 1024)
        var totalSentSize = 0
        
        
        for (index, item) in binFileChunks.enumerated(){
            try Task.checkCancellation()
            var binary = Binary()
            binary.writeInt(Int16(index + 1), littleEndian: true)
            binary.writeBytes([UInt8](item))
            resp = try await peripheralManager.request(address: address, cmd: .dfuPacket, data: Data(binary.getAllBytes()), callback: {[weak self] sent, total in
                debugPrint("DFUProcess DFU Progress:\(sent),\(total)")
                if let address = self?.address{
                    self?.continuation?.yield(DFUStateEvent(
                        address: address,
                        state: DFUState.progress,
                        sentSize: Int64(totalSentSize + sent),
                        totalSize: Int64(fileSize)
                    ))
                }
                
            })
            if resp.data?.first != ASK_CODE_SUCCESS{
                throw BleError.dfuDataError(msg: "CMD.DFU_PACKET response error:\(resp.data?.hexEncodedString(separator: "_"))")
            }
            totalSentSize += item.count - 6
            try Task.checkCancellation()
        }
        try Task.checkCancellation()
        continuation?.yield(DFUStateEvent(
            address: address,
            state: DFUState.progress,
            sentSize:Int64(fileSize),
            totalSize: Int64(fileSize)
        ))
        try Task.checkCancellation()
        resp = try await peripheralManager.request(address: address, cmd: .dfuCheck, data: nil)
        if resp.data?.first != ASK_CODE_SUCCESS{
            throw BleError.dfuDataError(msg: "CMD.DFU_CHECK response error:\(resp.data?.hexEncodedString(separator: "_"))")
        }
        peripheralManager.write(address: address, cmd: .dfuEnd, value: nil) { _ in
            
        }
        continuation?.yield(DFUStateEvent(
            address: address,
            state: DFUState.completed
        ))
    }
    
}
